/*
 * Copyright (C) 2008 Universidade Federal de Campina Grande
 *  
 * This file is part of OurGrid. 
 *
 * OurGrid is free software: you can redistribute it and/or modify it under the
 * terms of the GNU Lesser General Public License as published by the Free 
 * Software Foundation, either version 3 of the License, or (at your option) 
 * any later version. 
 * 
 * This program is distributed in the hope that it will be useful, but WITHOUT 
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or 
 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License
 * for more details. 
 * 
 * You should have received a copy of the GNU Lesser General Public License 
 * along with this program. If not, see <http://www.gnu.org/licenses/>.
 * 
 */
package org.ourgrid.worker.controller;

import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.TimeUnit;

import org.ourgrid.common.executor.Executor;
import org.ourgrid.common.executor.ExecutorFactory;
import org.ourgrid.common.interfaces.control.WorkerControl;
import org.ourgrid.common.interfaces.control.WorkerControlClient;
import org.ourgrid.common.interfaces.management.WorkerManagementClient;
import org.ourgrid.common.interfaces.status.WorkerCompleteStatus;
import org.ourgrid.common.interfaces.status.WorkerStatusProvider;
import org.ourgrid.common.interfaces.to.WorkerStatus;
import org.ourgrid.reqtrace.Req;
import org.ourgrid.worker.WorkerConfiguration;
import org.ourgrid.worker.WorkerConstants;
import org.ourgrid.worker.collectors.gmond.CollectorThread;
import org.ourgrid.worker.collectors.gmond.GMondCollectorController;
import org.ourgrid.worker.collectors.gmond.GMondUtils;
import org.ourgrid.worker.collectors.interfaces.WorkerCollectorController;
import org.ourgrid.worker.controller.actions.ReportWorkAccountingAction;
import org.ourgrid.worker.controller.actions.ReportWorkerSpecAction;
import org.ourgrid.worker.controller.actions.idlenessdetector.IdlenessDetectorActionFactory;
import org.ourgrid.worker.controller.actions.idlenessdetector.ScheduleTime;
import org.ourgrid.worker.controller.actions.idlenessdetector.ScheduleTimeParser;
import org.ourgrid.worker.controller.processors.GetFileInfoProcessor;
import org.ourgrid.worker.controller.processors.GetFilesProcessor;
import org.ourgrid.worker.controller.processors.RemoteExecuteProcessor;
import org.ourgrid.worker.dao.EnvironmentDAO;
import org.ourgrid.worker.dao.FileTransferDAO;
import org.ourgrid.worker.dao.GridProcessDAO;
import org.ourgrid.worker.dao.IdlenessDetectorDAO;
import org.ourgrid.worker.dao.WorkAccountingDAO;
import org.ourgrid.worker.dao.WorkerMessageProcessorDAO;
import org.ourgrid.worker.dao.WorkerSpecDAO;
import org.ourgrid.worker.dao.WorkerSpecListenerDAO;
import org.ourgrid.worker.dao.WorkerStatusDAO;
import org.ourgrid.worker.messages.ControlMessages;
import org.ourgrid.worker.receiver.IdlenessDetectorWorkerControlClient;
import org.ourgrid.worker.receiver.WorkerManagementReceiver;

import br.edu.ufcg.lsd.commune.Application;
import br.edu.ufcg.lsd.commune.api.MonitoredBy;
import br.edu.ufcg.lsd.commune.container.ObjectDeployment;
import br.edu.ufcg.lsd.commune.container.control.ApplicationControlClient;
import br.edu.ufcg.lsd.commune.container.control.ApplicationServerController;
import br.edu.ufcg.lsd.commune.container.control.ControlOperationResult;
import br.edu.ufcg.lsd.commune.container.servicemanager.dao.DAO;
import br.edu.ufcg.lsd.commune.identification.DeploymentID;

/**
 * Perform the Worker component control actions.
 */
@Req("REQ010")
public class WorkerComponentController extends ApplicationServerController implements WorkerControl, WorkerStatusProvider {


	public String getComponentName() {
		return "Worker";
	}
	
	protected void createDAOs() {
		createDAO(WorkerStatusDAO.class);
		createDAO(EnvironmentDAO.class);
		createDAO(FileTransferDAO.class);
		createDAO(GridProcessDAO.class);
		createDAO(WorkerMessageProcessorDAO.class);
		createDAO(IdlenessDetectorDAO.class);
		createDAO(WorkAccountingDAO.class);
		createDAO(WorkerSpecDAO.class);
		createDAO(WorkerSpecListenerDAO.class);
		
	}
	
	private void createDAO(Class<? extends DAO> clazz) {
		getServiceManager().createDAO(clazz);
	}
	
	private void createMessageProcessors() {
		getWorkerMessageProcessorDAO().
			putMessageProcessor(WorkerConstants.GET_FILE_INFO_ACTION_NAME, new GetFileInfoProcessor());
		getWorkerMessageProcessorDAO().
			putMessageProcessor(WorkerConstants.REMOTE_EXECUTE_ACTION_NAME, new RemoteExecuteProcessor());
		getWorkerMessageProcessorDAO().
			putMessageProcessor(WorkerConstants.GET_FILES_ACTION_NAME, new GetFilesProcessor());
	}

	protected void createServices() {
		getServiceManager().deploy(WorkerConstants.LOCAL_WORKER_MANAGEMENT, new WorkerManagementReceiver());
		getServiceManager().deploy(WorkerConstants.WORKER_COLLECTOR, new GMondCollectorController());
		createMessageProcessors();
	}
	
	protected void createMonitors() {
		getServiceManager().deploy(WorkerConstants.LOCAL_WORKER_MANAGEMENT_CLIENT_MONITOR, 
				new WorkerManagementClientFailureController());
	}
	
	private WorkerStatusDAO getWorkerStatusDAO() {
		return getDAO(WorkerStatusDAO.class);
	}
	
	private WorkerMessageProcessorDAO getWorkerMessageProcessorDAO() {
		return getDAO(WorkerMessageProcessorDAO.class);
	}
	
	private <T extends DAO> T getDAO(Class<T> class1) {
		return getServiceManager().getDAO(class1);
	}
	
	protected void startComponent() throws Exception {
		
		createExecutor();
		
		createRepeatedActions();
		
		createPropertiesCollector();

		getServiceManager().getLog().info(ControlMessages.getSuccessfullyStartedWorkerMessage());
		
		getWorkerStatusDAO().setStatus(isIdlenessDetectorOn() || useIdlenessSchedule() ? WorkerStatus.OWNER : WorkerStatus.IDLE);
	}

	private void createPropertiesCollector() throws Exception {

		if (isPropertiesCollectorOn()) {
			ObjectDeployment objectDeployment = getServiceManager().getObjectDeployment(WorkerConstants.WORKER_COLLECTOR);
			WorkerCollectorController collectorProxy = (WorkerCollectorController) objectDeployment.getProxy();
			
			String propGmondFile = GMondUtils.getInstance().getGmondConfigFile();
			
			CollectorThread collectorThread = new CollectorThread(collectorProxy, GMondUtils.getInstance().getGmondAddress(),
					GMondUtils.getInstance().getGmondPort(propGmondFile));
			collectorThread.start();
		}
	}

	private void createRepeatedActions() {
		
		getServiceManager().addActionForRepetition(
				WorkerConstants.REPORT_WORK_ACCOUNTING_ACTION_NAME, new ReportWorkAccountingAction());
		
		getServiceManager().addActionForRepetition(
				WorkerConstants.REPORT_WORKER_SPEC_ACTION_NAME, new ReportWorkerSpecAction());

		
		if (isIdlenessDetectorOn() || useIdlenessSchedule()) {
			createIdlenessDetectorAction();
		} 
	}

	private void createIdlenessDetectorAction() {
		
		String propIdlenessScheduleTime = 
			(String) getServiceManager().getContainerContext().getProperty(WorkerConfiguration.PROP_IDLENESS_SCHEDULE_TIME);
		
		List<ScheduleTime> scheduleTimes = new LinkedList<ScheduleTime>();
		
		if (useIdlenessSchedule()) {
			ScheduleTimeParser scheduleTimeParser = new ScheduleTimeParser(propIdlenessScheduleTime);
			scheduleTimes = scheduleTimeParser.parseScheduleTimes();
		}
		
		getServiceManager().deploy(WorkerConstants.IDLENESS_DETECTOR_WORKER_CONTROL_CLIENT, 
				new IdlenessDetectorWorkerControlClient());
		
		IdlenessDetectorDAO idlenessDetectorDAO = getServiceManager().getDAO(IdlenessDetectorDAO.class);
		idlenessDetectorDAO.setActive(isIdlenessDetectorOn());
		idlenessDetectorDAO.setIdlenessTime(getServiceManager().getContainerContext().parseLongProperty(WorkerConfiguration.PROP_IDLENESS_TIME) * 1000);
		idlenessDetectorDAO.setScheduleTimes(scheduleTimes);
		
		getServiceManager().addActionForRepetition(WorkerConstants.IDLENESSDETECTOR_ACTION_NAME, 
				new IdlenessDetectorActionFactory(IdlenessDetectorController.getInstance(),	idlenessDetectorDAO).createIdlenessDetectorAction());
		getServiceManager().scheduleActionWithFixedDelay(WorkerConstants.IDLENESSDETECTOR_ACTION_NAME, 
				WorkerConstants.IDLENESSDETECTOR_VERIFICATION_TIME, TimeUnit.MILLISECONDS);
	}

	private void createExecutor() {
		
		Executor executor = new ExecutorFactory(getServiceManager().getContainerContext(),
				getServiceManager().getLog()).buildExecutor();
		
		getDAO(GridProcessDAO.class).setExecutor(executor);
	}

	public void stop(boolean callExit, boolean force, 
			@MonitoredBy(Application.CONTROL_OBJECT_NAME) ApplicationControlClient client) {

		String senderPublicKey = getServiceManager().getSenderPublicKey();
		
		if (validateStopSenderPublicKey(client, senderPublicKey) && 
				canComponentBeUsed(client)){
			
			WorkerController.getInstance().interruptWorking(getServiceManager(), true);
			
			getServiceManager().getLog().info(ControlMessages.getSuccessfullyShutdownWorkerMessage());
			
			super.stop(callExit, force, client);
		}
	}

	@Req("REQ087")
	public void pause(WorkerControlClient client) {

		if (!canComponentBeUsed((ApplicationControlClient) client)) {
			return;
		}
		
		String senderPublicKey = getServiceManager().getSenderPublicKey();
		
		if (!getServiceManager().isThisMyPublicKey(senderPublicKey)) {
			getServiceManager().getLog().warn(ControlMessages.getUnknownEntityTryingToPauseWorkerMessage(senderPublicKey));
			return;
		}
		
		WorkerManagementClient wmc = getWorkerStatusDAO().getMasterPeer();
		
		WorkerStatus actualStatus = getWorkerStatusDAO().getStatus();
		
		if(!actualStatus.equals(WorkerStatus.OWNER)) {
			getServiceManager().getLog().info(ControlMessages.getWorkerPausedMessage());
			getServiceManager().getLog().debug(ControlMessages.getWorkerStatusChangedMessage(actualStatus, WorkerStatus.OWNER));
			
			if(getWorkerStatusDAO().isAllocated()) {
				WorkerController.getInstance().interruptWorking(getServiceManager(), true);
			}
			
			getWorkerStatusDAO().setStatus(WorkerStatus.OWNER);
		}

		if (wmc != null) {
			wmc.statusChanged(getWorkerStatusDAO().getStatus());
		}
		
		client.operationSucceed(new ControlOperationResult());
	}
	

	@Req("REQ088")
	public void resume(WorkerControlClient client) {

		if (!canComponentBeUsed((ApplicationControlClient) client)) {
			return;
		}
		
		String senderPublicKey = getServiceManager().getSenderPublicKey();
		
		if (!getServiceManager().isThisMyPublicKey(senderPublicKey)) {
			getServiceManager().getLog().warn(ControlMessages.getUnknownEntityTryingToResumeWorkerMessage(senderPublicKey));	
			return;
		}
		
		if(getWorkerStatusDAO().getStatus().equals(WorkerStatus.OWNER)) {
			getServiceManager().getLog().info(ControlMessages.getOwnerWorkerResumedMessage());
			getServiceManager().getLog().debug(ControlMessages.getWorkerStatusChangedMessage(
					WorkerStatus.OWNER, WorkerStatus.IDLE));
			
			getWorkerStatusDAO().setStatus(WorkerStatus.IDLE);

			WorkerManagementClient wmc = getWorkerStatusDAO().getMasterPeer();
			
			if (wmc != null) {
				wmc.statusChanged(getWorkerStatusDAO().getStatus());
			}
		}
		
		client.operationSucceed(new ControlOperationResult());
	}

	@Override
	protected boolean validateStartSenderPublicKey(ApplicationControlClient client, String senderPublicKey) {
		
		if(!getServiceManager().isThisMyPublicKey(senderPublicKey)) {
			getServiceManager().getLog().warn(ControlMessages.getUnknownEntityTryingToStartWorkerMessage(senderPublicKey));
			return false;
		}
		return true;
	}
	
	protected boolean validateStopSenderPublicKey(ApplicationControlClient client, String senderPublicKey) {
		
		if(!getServiceManager().isThisMyPublicKey(senderPublicKey)) {
			getServiceManager().getLog().warn(ControlMessages.getUnknownEntityTryingToStopWorkerMessage(senderPublicKey));
			return false;
		}
		return true;
	}

	/**
	 * Retrieves to the callback client complete info on this worker
	 * @param client The client that requested info
	 */
	@Req("REQ095")
	public void getCompleteStatus(WorkerControlClient client) {
		if (canStatusBeUsed()) {
			client.hereIsCompleteStatus(getCompleteStatus());
		}
	}

	@Req("REQ094")
	public void getStatus(WorkerControlClient client) {
		if (canStatusBeUsed()) {
			client.hereIsStatus(getStatus());
		}
	}

	@Req("REQ093")
	public void getMasterPeer(WorkerControlClient client) {
		if (canStatusBeUsed()) {
		client.hereIsMasterPeer(getMasterPeerID());
		}
	}
	
	@Req("REQ095")
	private WorkerCompleteStatus getCompleteStatus() {

		return new WorkerCompleteStatus(getServiceManager().getContainerDAO().getUpTime(),
				WorkerConfiguration.toString(getServiceManager().getContainerContext()),
				getMasterPeerID(), getStatus(), getCurrentPlaypenDirPath(),	getCurrentStorageDirPath());
	}
	
	@Req("REQ095")
	private String getCurrentPlaypenDirPath() {
		String playpenDir = getEnvironmentDAO().getPlaypenDir();
		if (playpenDir != null) {
			return playpenDir;
		}
		return EnvironmentController.getInstance().getPlaypenRoot(getServiceManager().getContainerContext());
	}
	
	@Req("REQ095")
	private String getCurrentStorageDirPath() {
		String storageDir = getEnvironmentDAO().getStorageDir();
		if (storageDir != null) {
			return storageDir;
		}
		return EnvironmentController.getInstance().getStorageRoot(getServiceManager().getContainerContext());
	}
	
	@Req("REQ095")
	private DeploymentID getMasterPeerID() {
		return (getWorkerDAO().getMasterPeer() == null) ? null : getServiceManager().getStubDeploymentID(
				getWorkerDAO().getMasterPeer());
	}
	
	@Req("REQ095")
	private WorkerStatus getStatus() {
		return getWorkerDAO().getStatus();
	}
	
	private WorkerStatusDAO getWorkerDAO() {
		return getDAO(WorkerStatusDAO.class);
	}
	
	private EnvironmentDAO getEnvironmentDAO() {
		return getDAO(EnvironmentDAO.class);
	}
	
	private boolean isIdlenessDetectorOn() {
		String propIdlenessDetector = 
			(String) getServiceManager().getContainerContext().getProperty(WorkerConfiguration.PROP_IDLENESS_DETECTOR);
		
		return (propIdlenessDetector == null) ? 
			false :
			!propIdlenessDetector.equals(WorkerConfiguration.DEF_PROP_IDLENESS_DETECTOR);
	}
	
	private boolean useIdlenessSchedule() {
		String propUseIdlenessSchedule = 
			(String) getServiceManager().getContainerContext().getProperty(WorkerConfiguration.PROP_USE_IDLENESS_SCHEDULE);
		
		return (propUseIdlenessSchedule == null) ? 
				false :
				!propUseIdlenessSchedule.equals(WorkerConfiguration.DEF_PROP_USE_IDLENESS_SCHEDULE);
	}
	
	private boolean isPropertiesCollectorOn() {
		String propCollector = 
			(String) getServiceManager().getContainerContext().getProperty(WorkerConfiguration.PROP_COLLECTOR);
		
		return (propCollector == null) ? false: 
			!propCollector.equals(WorkerConfiguration.DEF_PROP_COLLECTOR);
		
	}

}